package org.owasp.esapi.reference;

import java.util.Map;

import org.owasp.esapi.AccessControlRule;
import org.owasp.esapi.AccessController;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Logger;
import org.owasp.esapi.errors.AccessControlException;
import org.owasp.esapi.reference.accesscontrol.policyloader.ACRPolicyFileLoader;
import org.owasp.esapi.reference.accesscontrol.policyloader.PolicyDTO;

public class DefaultAccessController implements AccessController {
    private Map ruleMap;

    private static volatile AccessController singletonInstance = null;

    public static AccessController getInstance() throws AccessControlException {
        if ( singletonInstance == null ) {
            synchronized ( DefaultAccessController.class ) {
                if ( singletonInstance == null ) {
                    singletonInstance = new DefaultAccessController();
                }
            }
        }
        return singletonInstance;
    }

    protected final Logger logger = ESAPI.getLogger("DefaultAccessController");

    private DefaultAccessController() throws AccessControlException {
        ACRPolicyFileLoader policyDescriptor = new ACRPolicyFileLoader();
        PolicyDTO policyDTO = policyDescriptor.load();
        ruleMap = policyDTO.getAccessControlRules();
    }

    /**
     * {@inheritDoc}
     */
    public boolean isAuthorized(Object key, Object runtimeParameter) {
        try {
            AccessControlRule rule = (AccessControlRule)ruleMap.get(key);
            if(rule == null) {
                throw new AccessControlException("Access Denied",
                        "AccessControlRule was not found for key: " + key);
            }
            if(logger.isDebugEnabled()){ logger.debug(Logger.EVENT_SUCCESS, "Evaluating Authorization Rule \"" + key + "\" Using class: " + rule.getClass().getCanonicalName()); }
            return rule.isAuthorized(runtimeParameter);
        } catch(Exception e) {
            try {
                //Log the exception by throwing and then catching it.
                //TODO figure out what which string goes where.
                throw new AccessControlException("Access Denied",
                    "An unhandled Exception was " +
                    "caught, so access is denied.",
                    e);
            } catch(AccessControlException ace) {
                //the exception was just logged. There's nothing left to do.
            }
            return false; //fail closed
        }
    }

    /** {@inheritDoc} */
    public void assertAuthorized(Object key, Object runtimeParameter) throws AccessControlException {
        boolean isAuthorized;
        try {
            AccessControlRule rule = (AccessControlRule)ruleMap.get(key);
            if(rule == null) {
                throw new AccessControlException("Access Denied",
                        "AccessControlRule was not found for key: " + key);
            }
            if(logger.isDebugEnabled()){ logger.debug(Logger.EVENT_SUCCESS, "Asserting Authorization Rule \"" + key + "\" Using class: " + rule.getClass().getCanonicalName()); }
            isAuthorized = rule.isAuthorized(runtimeParameter);
        } catch(Exception e) {
            //TODO figure out what which string goes where.
            throw new AccessControlException("Access Denied", "An unhandled Exception was " +
                    "caught, so access is denied." +
                    "AccessControlException.",
                    e);
        }
        if(!isAuthorized) {
            throw new AccessControlException("Access Denied",
                    "Access Denied for key: " + key +
                    " runtimeParameter: " + runtimeParameter);
        }
    }

    /** {@inheritDoc} */
    public void assertAuthorizedForData(String action, Object data)
            throws AccessControlException {
        this.assertAuthorized("AC 1.0 Data", new Object[] {action, data});
    }

    /**
     * {@inheritDoc}
     * @deprecated
     */
    @Deprecated
    public void assertAuthorizedForFile(String filepath)
            throws AccessControlException {
        this.assertAuthorized("AC 1.0 File", new Object[] {filepath});
    }

    /** {@inheritDoc} */
    public void assertAuthorizedForFunction(String functionName)
            throws AccessControlException {
        this.assertAuthorized("AC 1.0 Function", new Object[] {functionName});
    }

    /** {@inheritDoc} */
    public void assertAuthorizedForService(String serviceName)
            throws AccessControlException {
        this.assertAuthorized("AC 1.0 Service", new Object[] {serviceName});
    }

    /** {@inheritDoc} */
    public void assertAuthorizedForURL(String url)
            throws AccessControlException {
        this.assertAuthorized("AC 1.0 URL", new Object[] {url});
    }

    /** {@inheritDoc} */
    public boolean isAuthorizedForData(String action, Object data) {
        return this.isAuthorized("AC 1.0 Data", new Object[] {action, data});
    }

    /** {@inheritDoc} */
    public boolean isAuthorizedForFile(String filepath) {
        return this.isAuthorized("AC 1.0 File", new Object[] {filepath});
    }

    /** {@inheritDoc} */
    public boolean isAuthorizedForFunction(String functionName) {
        return this.isAuthorized("AC 1.0 Function", new Object[] {functionName});
    }

    /** {@inheritDoc} */
    public boolean isAuthorizedForService(String serviceName) {
        return this.isAuthorized("AC 1.0 Service", new Object[] {serviceName});
    }

    /** {@inheritDoc} */
    public boolean isAuthorizedForURL(String url) {
        return this.isAuthorized("AC 1.0 URL", new Object[] {url});
    }
}
